/*
 * Copyright (C) 2007-2012 by XDEV Software, All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 3.0 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
 
package xdev.ui;


import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Font;
import java.awt.LayoutManager;
import java.awt.event.ContainerEvent;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JMenu;

import xdev.Application;
import xdev.db.DBConnection;
import xdev.db.DBException;
import xdev.db.QueryInfo;
import xdev.db.Transaction;
import xdev.db.sql.Condition;
import xdev.db.sql.SELECT;
import xdev.db.sql.WHERE;
import xdev.lang.cmd.Query;
import xdev.net.NetUtils;
import xdev.ui.FormularComponent.ValueChangeListener;
import xdev.ui.FormularUtils.FormularValue;
import xdev.ui.FormularUtils.ParameterContainer;
import xdev.ui.UIUtils.MessageDialogType;
import xdev.ui.event.ContainerHierarchyListener;
import xdev.ui.event.FormularEvent;
import xdev.ui.event.FormularListener;
import xdev.util.CollectionUtils;
import xdev.vt.KeyValues;
import xdev.vt.VirtualTable;
import xdev.vt.VirtualTable.VirtualTableRow;
import xdev.vt.VirtualTableException;


/**
 * The {@link XdevFormular} is a GUI container component.
 * 
 * <p>
 * The {@link XdevFormular} can be used to display a row of a
 * {@link VirtualTable}. Therefore the {@link XdevFormular} must contain
 * {@link FormularComponent}s that are mapped to the columns of the
 * {@link VirtualTable} you want to display. This can be done by the XDEV IDE or
 * manually.
 * </p>
 * 
 * <p>
 * The {@link XdevFormular} also manages n:m-relations. Therefore
 * {@link ManyToManyComponent}s can be added into this container.
 * </p>
 * 
 * <p>
 * The {@link XdevFormular} provides methods to:
 * <ul>
 * <li>display {@link VirtualTableRow}s like
 * {@link #setModel(xdev.vt.VirtualTable.VirtualTableRow)}</li>
 * <li>insert {@link VirtualTableRow}s like {@link #insert(boolean)}</li>
 * <li>update (before loaded) {@link VirtualTableRow}s like
 * {@link #update(boolean)}</li>
 * <li>delete (before loaded) {@link VirtualTableRow}s like
 * {@link #delete(boolean)}</li>
 * </ul>
 * </p>
 * 
 * @author XDEV Software
 * 
 * @see FormularListener
 * @see FormularComponent
 * @see MasterDetailComponent
 * @see ManyToManyComponent
 * 
 * @since 2.0
 */
public class XdevFormular extends XdevContainer
{
	/**
	 * Current working state of a {@link XdevFormular}.
	 * 
	 * @see XdevFormular#getWorkingState()
	 * 
	 * @since 3.1
	 */
	public static enum WorkingState
	{
		/**
		 * No special action is in progress.
		 */
		IDLE,
		
		/**
		 * The formular's model is currently beeing updated.
		 */
		ADJUSTING_MODEL,
		
		/**
		 * The formular's values are currently beeing restored.
		 */
		RESTORING
	}
	
	private WorkingState		workingState							= WorkingState.IDLE;
	
	private VirtualTableRow		virtualTableRow							= null;
	
	/**
	 * Identifies a change in the saveStateAfterModelUpdate property.
	 */
	public final static String	SAVE_STATE_AFTER_MODEL_UPDATE_PROPERTY	= "saveStateAfterModelUpdate";
	
	private boolean				saveStateAfterModelUpdate				= true;
	
	private Map<String, Object>	hiddenFields;
	
	
	/**
	 * Create a new {@link XdevFormular} with no layout manager.
	 * 
	 * <p>
	 * Alias for <code>XdevFormular(null)</code>
	 * </p>
	 * 
	 * @see #XdevFormular(LayoutManager)
	 * 
	 */
	public XdevFormular()
	{
		super();
	}
	
	
	/**
	 * Create a new {@link XdevFormular} with the specified layout manager.
	 * 
	 * @param layout
	 *            the LayoutManager to use
	 */
	public XdevFormular(LayoutManager layout)
	{
		super(layout);
	}
	
	/*
	 * Handler for FormularListener#formularComponentValueChanged(FormularEvent)
	 * 
	 * @since 3.1
	 */
	{
		addContainerListener(new ContainerHierarchyListener()
		{
			Map<FormularComponent, ValueChangeListener>	listenerMap	= new HashMap();
			
			
			@Override
			public void componentAddedInHierarchy(ContainerEvent e)
			{
				Component child = e.getChild();
				if(child instanceof Container)
				{
					UIUtils.lookupComponentTree((Container)child,new ComponentTreeVisitor()
					{
						@Override
						public Object visit(Component cpn)
						{
							if(cpn instanceof FormularComponent)
							{
								add((FormularComponent)cpn);
							}
							
							return null;
						}
					});
				}
				else if(child instanceof FormularComponent)
				{
					add((FormularComponent)child);
				}
			}
			
			
			void add(final FormularComponent formularComponent)
			{
				if(!listenerMap.containsKey(formularComponent))
				{
					ValueChangeListener valueChangeListener = new ValueChangeListener()
					{
						@Override
						public void valueChanged(Object eventObject)
						{
							fireFormularComponentValueChanged(formularComponent,eventObject);
						}
					};
					formularComponent.addValueChangeListener(valueChangeListener);
					listenerMap.put(formularComponent,valueChangeListener);
				}
			}
			
			
			@Override
			public void componentRemovedInHierarchy(ContainerEvent e)
			{
			}
		});
	}
	
	
	/**
	 * Registers a {@link FormularListener} so that it will receive formular
	 * events.
	 * 
	 * @param l
	 *            the listener to register
	 */
	public void addFormularListener(FormularListener l)
	{
		listenerList.add(FormularListener.class,l);
	}
	
	
	/**
	 * Removes the listener from the listener list of this formular.
	 * 
	 * @param l
	 *            the listener to remove
	 */
	public void removeFormularListener(FormularListener l)
	{
		listenerList.remove(FormularListener.class,l);
	}
	
	
	protected void fireModelChanged()
	{
		FormularListener[] listeners = listenerList.getListeners(FormularListener.class);
		if(listeners != null && listeners.length > 0)
		{
			FormularEvent event = new FormularEvent(this);
			for(FormularListener listener : listeners)
			{
				listener.formularModelChanged(event);
			}
		}
	}
	
	
	/**
	 * @since 3.1
	 */
	protected void fireFormularComponentValueChanged(FormularComponent formularComponent,
			Object formularComponentEventObject)
	{
		FormularListener[] listeners = listenerList.getListeners(FormularListener.class);
		if(listeners != null && listeners.length > 0)
		{
			FormularEvent event = new FormularEvent(this,formularComponent,
					formularComponentEventObject,workingState);
			for(FormularListener listener : listeners)
			{
				listener.formularComponentValueChanged(event);
			}
		}
	}
	
	
	protected void fireSavePerformed()
	{
		FormularListener[] listeners = listenerList.getListeners(FormularListener.class);
		if(listeners != null && listeners.length > 0)
		{
			FormularEvent event = new FormularEvent(this);
			for(FormularListener listener : listeners)
			{
				listener.formularSavePerformed(event);
			}
		}
		
		UIUtils.lookupComponentTree(this,new ComponentTreeVisitor()
		{
			@Override
			public Object visit(Component cpn)
			{
				if(cpn instanceof ManyToManyComponent)
				{
					((ManyToManyComponent)cpn).refresh(virtualTableRow);
				}
				
				return null;
			}
		});
	}
	
	
	/**
	 * Determines of the {@link FormularComponent} <code>formCpn</code> should
	 * be used in this formular's actions.
	 * 
	 * @param formCpn
	 *            the {@link FormularComponent} to check
	 * @return <code>true</code> if <code>formCpn</code> should be used,
	 *         <code>false</code> otherwise
	 */
	protected boolean use(FormularComponent formCpn)
	{
		// since 3.1, see FORMULAR_SKIP
		if(formCpn instanceof JComponent
				&& Boolean.TRUE.equals(((JComponent)formCpn).getClientProperty(FORMULAR_SKIP)))
		{
			return false;
		}
		
		return true;
	}
	
	
	<T> T lookupComponentTree(final ComponentTreeVisitor<T, Component> _visitor)
	{
		ComponentTreeVisitor<T, Component> visitor = new ComponentTreeVisitor<T, Component>()
		{
			@Override
			public T visit(Component cpn)
			{
				T ret = null;
				
				if(!(cpn instanceof FormularComponent && !use((FormularComponent)cpn)))
				{
					ret = _visitor.visit(cpn);
				}
				
				return ret;
			}
		};
		return lookupComponentTreeImpl(this,visitor);
	}
	
	
	private <T> T lookupComponentTreeImpl(Container parent,
			ComponentTreeVisitor<T, Component> visitor)
	{
		T value = null;
		
		value = visitor.visit(parent);
		
		if(value == null)
		{
			if(parent instanceof JMenu)
			{
				parent = ((JMenu)parent).getPopupMenu();
			}
			
			int c = parent.getComponentCount();
			for(int i = 0; i < c && value == null; i++)
			{
				Component cpn = parent.getComponent(i);
				if(cpn instanceof Container)
				{
					// don't traverse through sub-forms
					if(!(cpn instanceof XdevFormular))
					{
						value = lookupComponentTreeImpl((Container)cpn,visitor);
					}
				}
				else
				{
					value = visitor.visit(cpn);
				}
			}
		}
		
		return value;
	}
	
	
	/**
	 * Saves the state of all {@link FormularComponent}s in this formular.
	 * 
	 * @see #reset()
	 * @see FormularComponent#saveState()
	 */
	public void saveState()
	{
		lookupComponentTree(new ComponentTreeVisitor<Object, Component>()
		{
			public Object visit(Component cpn)
			{
				if(cpn instanceof ManyToManyComponent)
				{
					((ManyToManyComponent)cpn).saveState();
				}
				else if(cpn instanceof FormularComponent)
				{
					((FormularComponent)cpn).saveState();
				}
				
				return null;
			}
		});
	}
	
	
	/**
	 * Resets this {@link XdevFormular} / the mapped components in the
	 * {@link XdevFormular} to the default values of the components.
	 * <p>
	 * This method is a synonym for {@link #reset()}
	 * 
	 * @see #saveState()
	 * @see FormularComponent#restoreState()
	 */
	public void restoreState()
	{
		workingState = WorkingState.RESTORING;
		
		try
		{
			lookupComponentTree(new ComponentTreeVisitor<Object, Component>()
			{
				public Object visit(Component cpn)
				{
					if(cpn instanceof ManyToManyComponent)
					{
						((ManyToManyComponent)cpn).restoreState();
					}
					else if(cpn instanceof FormularComponent)
					{
						((FormularComponent)cpn).restoreState();
					}
					
					return null;
				}
			});
		}
		finally
		{
			workingState = WorkingState.IDLE;
		}
	}
	
	
	/**
	 * Resets this {@link XdevFormular} / the mapped components in the
	 * {@link XdevFormular} to the default values of the components.
	 * <p>
	 * This method is a synonym for {@link #restoreState()}
	 * 
	 * @see #saveState()
	 * @see FormularComponent#restoreState()
	 */
	public void reset()
	{
		restoreState();
	}
	
	
	/**
	 * Resets this {@link XdevFormular} / the mapped components in the
	 * {@link XdevFormular} to the default values of the specified
	 * {@link VirtualTable} <code>vt</code>.
	 * 
	 * <p>
	 * Alias for <code>setData(vt.createRow(),vt)</code>
	 * </p>
	 * 
	 * @param vt
	 *            {@link VirtualTable} to reset on.
	 * 
	 */
	public void setModel(VirtualTable vt)
	{
		reset(vt);
	}
	
	
	/**
	 * Resets this {@link XdevFormular} / the mapped components in the
	 * {@link XdevFormular} to the default values of the specified
	 * {@link VirtualTable} <code>vt</code>.
	 * 
	 * <p>
	 * Alias for <code>setModel(vt.createRow())</code>
	 * </p>
	 * 
	 * @param vt
	 *            {@link VirtualTable} to reset on.
	 * 
	 */
	public void reset(VirtualTable vt)
	{
		setModel(vt.createRow());
	}
	
	
	/**
	 * Fills this {@link XdevFormular} / the mapped components in the
	 * {@link XdevFormular} with the values provided by the specified
	 * <code>row</code> in the {@link VirtualTable} <code>vt</code>.
	 * 
	 * @param row
	 *            the row index of the {@link VirtualTable}
	 * @param vt
	 *            {@link VirtualTable} to take the data from.
	 * 
	 * @see VirtualTableRow
	 * @see #setModel(VirtualTable.VirtualTableRow)
	 */
	public void setModel(int row, VirtualTable vt)
	{
		setModel(vt.getRow(row));
	}
	
	
	/**
	 * Fills this {@link XdevFormular} / the mapped components in the
	 * {@link XdevFormular} with the values provided by the specified
	 * <code>row</code>.
	 * 
	 * @param virtualTableRow
	 *            {@link VirtualTableRow} to take the data from.
	 * 
	 * @see VirtualTableRow
	 * @see #setModel(int, VirtualTable)
	 */
	public void setModel(final VirtualTableRow virtualTableRow)
	{
		this.virtualTableRow = virtualTableRow;
		
		workingState = WorkingState.ADJUSTING_MODEL;
		
		try
		{
			final Map<String, Object> map = virtualTableRow.toMap();
			lookupComponentTree(new ComponentTreeVisitor()
			{
				public Object visit(Component cpn)
				{
					if(cpn instanceof ManyToManyComponent)
					{
						ManyToManyComponent nmComponent = (ManyToManyComponent)cpn;
						nmComponent.refresh(virtualTableRow);
					}
					else if(cpn instanceof FormularComponent)
					{
						FormularComponent fc = (FormularComponent)cpn;
						Object dataField = fc.getDataField();
						String name = dataField != null ? dataField.toString() : fc
								.getFormularName();
						if(name != null)
						{
							fc.setFormularValue(virtualTableRow.getVirtualTable(),map);
						}
					}
					
					return null;
				}
			});
		}
		finally
		{
			if(saveStateAfterModelUpdate)
			{
				saveState();
			}
			
			workingState = WorkingState.IDLE;
			
			fireModelChanged();
		}
	}
	
	
	/**
	 * Sets if the formular should save its state after an update of the model.
	 * 
	 * @param saveStateAfterModelUpdate
	 *            <code>true</code> if the state should be saved,
	 *            <code>false</code> otherwise
	 * 
	 * @see #setModel(VirtualTable.VirtualTableRow)
	 * @see #saveState()
	 */
	@BeanProperty(category = DefaultBeanCategories.DATA)
	public void setSaveStateAfterModelUpdate(boolean saveStateAfterModelUpdate)
	{
		if(this.saveStateAfterModelUpdate != saveStateAfterModelUpdate)
		{
			boolean oldValue = this.saveStateAfterModelUpdate;
			this.saveStateAfterModelUpdate = saveStateAfterModelUpdate;
			firePropertyChange(SAVE_STATE_AFTER_MODEL_UPDATE_PROPERTY,oldValue,
					saveStateAfterModelUpdate);
		}
	}
	
	
	/**
	 * Determines if the formular saves its state after an update of the model.
	 * 
	 * @return <code>true</code> if the state is saved, <code>false</code>
	 *         otherwise
	 * 
	 * @see #setModel(VirtualTable.VirtualTableRow)
	 * @see #saveState()
	 */
	public boolean getSaveStateAfterModelUpdate()
	{
		return saveStateAfterModelUpdate;
	}
	
	
	/**
	 * Adds an arbitrary name/value "hidden field" to this form.
	 * <p>
	 * If value is <code>null</code> this method will remove the field.
	 * 
	 * @param name
	 *            the new hidden field name
	 * @param value
	 *            the new hidden field value; if <code>null</code> this method
	 *            will remove the field
	 * @see #getHiddenField(String)
	 * @see #getHiddenFieldNames()
	 * @since 3.2
	 */
	public final void putHiddenField(String name, Object value)
	{
		if(name == null || name.length() == 0)
		{
			throw new IllegalArgumentException("name cannot be null or empty");
		}
		
		if(value != null)
		{
			if(hiddenFields == null)
			{
				hiddenFields = new HashMap();
			}
			hiddenFields.put(name,value);
		}
		else if(hiddenFields != null)
		{
			hiddenFields.remove(name);
		}
	}
	
	
	/**
	 * Returns the value of the hidden field with the specified name. Only
	 * fields added with <code>putHiddenField</code> will return a non-
	 * <code>null</code> value.
	 * 
	 * @param name
	 *            the being queried
	 * @return the value of this hidden field or <code>null</code>
	 * @see #putHiddenField(String, Object)
	 * @see #getHiddenFieldNames()
	 * @since 3.2
	 */
	public final Object getHiddenField(String name)
	{
		if(hiddenFields != null)
		{
			return hiddenFields.get(name);
		}
		return null;
	}
	
	
	/**
	 * Returns all hidden field names of this form.
	 * 
	 * @return all hidden field names of this form
	 * @see #putHiddenField(String, Object)
	 * @see #getHiddenField(String)
	 * @since 3.2
	 */
	public final Iterable<String> getHiddenFieldNames()
	{
		if(hiddenFields != null)
		{
			return hiddenFields.keySet();
		}
		
		return Collections.EMPTY_LIST;
	}
	
	
	/**
	 * Returns the working state of the formular.
	 * 
	 * @return the formular's current state
	 * 
	 * @since 3.1
	 */
	public WorkingState getWorkingState()
	{
		return workingState;
	}
	
	
	/**
	 * Checks if one of the {@link FormularComponent}'s or
	 * {@link ManyToManyComponent}'s state since the last call of
	 * {@link #saveState()} has changed.
	 * 
	 * @return <code>true</code> if one component's state has changed,
	 *         <code>false</code> otherwise
	 * 
	 * @see FormularComponent#hasStateChanged()
	 * @see ManyToManyComponent#hasStateChanged()
	 * 
	 * @since 3.1
	 */
	public boolean hasStateChanged()
	{
		Boolean changed = lookupComponentTree(new ComponentTreeVisitor<Boolean, Component>()
		{
			public Boolean visit(Component cpn)
			{
				if(cpn instanceof ManyToManyComponent
						&& ((ManyToManyComponent)cpn).hasStateChanged())
				{
					return true;
				}
				else if(cpn instanceof FormularComponent
						&& ((FormularComponent)cpn).hasStateChanged())
				{
					return true;
				}
				
				return null;
			}
		});
		
		return changed != null ? changed : false;
	}
	
	
	/**
	 * Returns a {@link Map} containing all values of this {@link XdevFormular}
	 * / the mapped components in the {@link XdevFormular}. The key of the
	 * {@link Map} is the component name; the value is the value of that
	 * component.
	 * 
	 * @param withNulls
	 *            <code>true</code> if null values should be returned; otherwise
	 *            <code>false</code>
	 * @return a {@link Map} containing all values of this {@link XdevFormular}
	 *         / the mapped components in the {@link XdevFormular}.
	 */
	public Map<String, Object> getData(boolean withNulls)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		if(hiddenFields != null)
		{
			map.putAll(hiddenFields);
		}
		
		FormularUtils.addData(this,map,withNulls);
		
		return map;
	}
	
	
	private List<ManyToManyComponent> getManyToManyComponents()
	{
		final List<ManyToManyComponent> list = new ArrayList();
		
		UIUtils.lookupComponentTree(this,new ComponentTreeVisitor<Object, Component>()
		{
			@Override
			public Object visit(Component cpn)
			{
				if(cpn instanceof ManyToManyComponent)
				{
					list.add((ManyToManyComponent)cpn);
				}
				
				return null;
			}
		});
		
		return list;
	}
	
	
	/**
	 * JavaDoc omitted for private method.
	 */
	private void checkVirtualTableRow() throws VirtualTableException
	{
		if(virtualTableRow == null)
		{
			throw new VirtualTableException("No data from a VirtualTable assigned");
		}
	}
	
	
	/**
	 * Returns the current assigned {@link VirtualTableRow}.
	 * 
	 * @return The current assigned {@link VirtualTableRow}
	 * 
	 * @see #setModel(VirtualTable)
	 * @see #setModel(VirtualTable.VirtualTableRow)
	 */
	public VirtualTableRow getVirtualTableRow()
	{
		return virtualTableRow;
	}
	
	
	/**
	 * Returns the assigned {@link VirtualTable} of this {@link XdevFormular}.
	 * 
	 * @return The current assigned {@link VirtualTable}
	 * 
	 */
	public VirtualTable getVirtualTable()
	{
		if(virtualTableRow != null)
		{
			return virtualTableRow.getVirtualTable();
		}
		
		return lookupVT();
	}
	
	
	/**
	 * <p>
	 * Alias for <code>save(true).</code>
	 * </p>
	 * 
	 * @throws VirtualTableException
	 *             if the new row can't be set to the {@link VirtualTable}.
	 * @throws DBException
	 *             if the new row can't be propagated to the underling database.
	 * 
	 * @see #save(boolean)
	 */
	public void save() throws VirtualTableException, DBException
	{
		save(true);
	}
	
	
	/**
	 * <p>
	 * Propagates the values of this {@link XdevFormular} / the mapped
	 * components in the {@link XdevFormular} to the set {@link VirtualTable} as
	 * new row if the row is new to the {@link VirtualTable}, otherwise an
	 * update is performed.
	 * </p>
	 * 
	 * 
	 * @param synchronizeDB
	 *            <code>true</code> if the new row should be propagated to the
	 *            underling database as well; otherwise <code>false</code>.
	 * @throws VirtualTableException
	 *             if the new row can't be set to the {@link VirtualTable}.
	 * @throws DBException
	 *             if the new row can't be propagated to the underling database.
	 * 
	 */
	public void save(final boolean synchronizeDB) throws VirtualTableException, DBException
	{
		if(virtualTableRow != null)
		{
			if(virtualTableRow.isNew())
			{
				insert(synchronizeDB);
			}
			else
			{
				update(synchronizeDB);
			}
		}
		else
		{
			VirtualTable vt = lookupVT();
			if(vt == null)
			{
				throw new NullPointerException("No VirtualTable found for Formular '" + getName()
						+ "'");
			}
			
			vt.addRow(getData(true),synchronizeDB);
			
			fireSavePerformed();
		}
	}
	
	
	/**
	 * Alias for <code>update(true)</code>.
	 * 
	 * @throws VirtualTableException
	 *             if the values can't be set to the {@link VirtualTable}.
	 * @throws DBException
	 *             if the values can't be propagated to the underling database.
	 * 
	 * @see #update(boolean)
	 */
	public void update() throws VirtualTableException, DBException
	{
		update(true);
	}
	
	
	/**
	 * <p>
	 * Propagates the values of this {@link XdevFormular} / the mapped
	 * components in the {@link XdevFormular} to the set {@link VirtualTable} /
	 * the set {@link VirtualTableRow}.
	 * </p>
	 * 
	 * <p>
	 * <strong>Warning:</strong> In order to be able to update a row, you need
	 * to set a {@link VirtualTableRow} for this {@link XdevFormular}. You can
	 * do this by calling {@link #setModel(int, VirtualTable)} or
	 * {@link #setModel(VirtualTable.VirtualTableRow)}.
	 * </p>
	 * 
	 * @param synchronizeDB
	 *            <code>true</code> if the changes should be propagated to the
	 *            underling database as well; otherwise <code>false</code>.
	 * @throws VirtualTableException
	 *             if the values can't be set to the {@link VirtualTable}.
	 * @throws DBException
	 *             if the values can't be propagated to the underling database.
	 * 
	 * @see #setModel(VirtualTable.VirtualTableRow)
	 */
	public void update(final boolean synchronizeDB) throws VirtualTableException, DBException
	{
		checkVirtualTableRow();
		
		final Map<String, Object> data = getData(true);
		final List<ManyToManyComponent> nmCpns = getManyToManyComponents();
		
		if(nmCpns.size() > 0)
		{
			if(synchronizeDB)
			{
				new Transaction(virtualTableRow.getVirtualTable().getDataSource())
				{
					@Override
					protected void write(DBConnection<?> connection) throws DBException
					{
						virtualTableRow.update(data,synchronizeDB,connection);
						for(ManyToManyComponent nmCpn : nmCpns)
						{
							nmCpn.save(synchronizeDB,connection);
						}
					}
				}.execute();
			}
			else
			{
				virtualTableRow.update(data,synchronizeDB);
				for(ManyToManyComponent nmCpn : nmCpns)
				{
					nmCpn.save(synchronizeDB,null);
				}
			}
		}
		else
		{
			virtualTableRow.update(data,synchronizeDB);
		}
		
		fireSavePerformed();
	}
	
	
	/**
	 * <p>
	 * Propagates the values of this {@link XdevFormular} / the mapped
	 * components in the {@link XdevFormular} to the specified
	 * {@link VirtualTable} <code>vt</code>, to the rows which matches the given
	 * {@link KeyValues} <code>pkv</code>.
	 * </p>
	 * 
	 * 
	 * 
	 * @param vt
	 *            {@link VirtualTable} to propagate the changes to
	 * @param keyValues
	 *            The values which identify the rows to update
	 * @param synchronizeDB
	 *            <code>true</code> if the changes should be propagated to the
	 *            underling database as well; otherwise <code>false</code>.
	 * @throws VirtualTableException
	 *             if the values can't be set to the {@link VirtualTable}.
	 * @throws DBException
	 *             if the values can't be propagated to the underling database.
	 */
	public void updateRowsInVT(VirtualTable vt, KeyValues keyValues, boolean synchronizeDB)
			throws VirtualTableException, DBException
	{
		vt.updateRows(getData(true),keyValues,synchronizeDB);
	}
	
	
	/**
	 * <p>
	 * Propagates the values of this {@link XdevFormular} / the mapped
	 * components in the {@link XdevFormular} to the set {@link VirtualTable} as
	 * new row.
	 * </p>
	 * 
	 * 
	 * @param vt
	 *            {@link VirtualTable} to propagate the new row to
	 * 
	 * @param synchronizeDB
	 *            <code>true</code> if the changes should be propagated to the
	 *            underling database as well; otherwise <code>false</code>.
	 * @throws VirtualTableException
	 *             if the values can't be set to the {@link VirtualTable}.
	 * @throws DBException
	 *             if the values can't be propagated to the underling database.
	 */
	public void insertRowInVT(VirtualTable vt, boolean synchronizeDB) throws VirtualTableException,
			DBException
	{
		vt.addRow(getData(true),synchronizeDB);
	}
	
	
	/**
	 * <p>
	 * Alias for <code>insert(true).</code>
	 * </p>
	 * 
	 * @throws VirtualTableException
	 *             if the new row can't be set to {@link VirtualTable}.
	 * @throws DBException
	 *             if the new row can't be propagated to underling database.
	 * 
	 * @see #insert(boolean)
	 */
	public void insert() throws VirtualTableException, DBException
	{
		insert(true);
	}
	
	
	/**
	 * <p>
	 * Propagates the values of this {@link XdevFormular} / the mapped
	 * components in the {@link XdevFormular} to the set {@link VirtualTable} as
	 * new row.
	 * </p>
	 * 
	 * @param synchronizeDB
	 *            <code>true</code> if the new row should be propagated to the
	 *            underling database as well; otherwise <code>false</code>.
	 * @throws VirtualTableException
	 *             if the new row can't be set to {@link VirtualTable}.
	 * @throws DBException
	 *             if the new row can't be propagated to underling database.
	 */
	public void insert(final boolean synchronizeDB) throws VirtualTableException, DBException
	{
		if(virtualTableRow != null)
		{
			final Map<String, Object> data = getData(true);
			final List<ManyToManyComponent> nmCpns = getManyToManyComponents();
			
			if(nmCpns.size() > 0)
			{
				if(synchronizeDB)
				{
					final DBConnection connection = virtualTableRow.getVirtualTable()
							.getDataSource().openConnection();
					
					try
					{
						new Transaction()
						{
							@Override
							protected DBConnection<?> getConnection() throws DBException
							{
								return connection;
							}
							
							
							@Override
							protected void write(DBConnection<?> connection) throws DBException
							{
								// insert master record first to get the
								// generated ids
								virtualTableRow.insert(data,synchronizeDB,connection);
								
								for(ManyToManyComponent nmCpn : nmCpns)
								{
									nmCpn.save(synchronizeDB,connection);
								}
							}
						}.execute();
					}
					finally
					{
						connection.close();
					}
				}
				else
				{
					virtualTableRow.insert(data,synchronizeDB);
					for(ManyToManyComponent nmCpn : nmCpns)
					{
						nmCpn.save(synchronizeDB,null);
					}
				}
			}
			else
			{
				virtualTableRow.insert(data,synchronizeDB);
			}
		}
		else
		{
			VirtualTable vt = lookupVT();
			if(vt == null)
			{
				throw new NullPointerException("No VirtualTable found for Formular '" + getName()
						+ "'");
			}
			
			vt.addRow(getData(true),synchronizeDB);
		}
		
		fireSavePerformed();
	}
	
	
	/**
	 * <p>
	 * Alias for <code>delete(true).</code>
	 * </p>
	 * 
	 * @throws VirtualTableException
	 *             if row can't be deleted from {@link VirtualTable}.
	 * @throws DBException
	 *             if the row can't be deleted from the underling database.
	 * 
	 * @see #delete(boolean)
	 */
	public void delete() throws VirtualTableException, DBException
	{
		delete(true);
	}
	
	
	/**
	 * <p>
	 * Deletes the {@link VirtualTableRow} represented by this
	 * {@link XdevFormular} from the set {@link VirtualTable}.
	 * </p>
	 * 
	 * <p>
	 * <strong>Warning:</strong> In order to be able to delete a row, you need
	 * to set a {@link VirtualTableRow} for this {@link XdevFormular}. You can
	 * do this by calling {@link #setModel(VirtualTable.VirtualTableRow)} or
	 * {@link #setModel(int, VirtualTable)}.
	 * </p>
	 * 
	 * @param synchronizeDB
	 *            <code>true</code> if the row should also be deleted from the
	 *            underling database as well; otherwise <code>false</code>.
	 * @throws VirtualTableException
	 *             if row can't be deleted from {@link VirtualTable}.
	 * @throws DBException
	 *             if the row can't be deleted from the underling database.
	 * 
	 */
	public void delete(boolean synchronizeDB) throws VirtualTableException, DBException
	{
		checkVirtualTableRow();
		
		virtualTableRow.delete(synchronizeDB);
	}
	
	
	/**
	 * JavaDoc omitted for private method.
	 */
	private VirtualTable lookupVT()
	{
		return UIUtils.lookupComponentTree(this,
				new ComponentTreeVisitor<VirtualTable, JComponent>()
				{
					public VirtualTable visit(JComponent cpn)
					{
						if(cpn instanceof FormularComponent)
						{
							return FormularUtils.getConnectedVT((FormularComponent)cpn);
						}
						
						return null;
					}
				},JComponent.class);
	}
	
	
	/**
	 * Returns <code>ture</code> if all values of all {@link FormularComponent}s
	 * of this {@link XdevFormular} could be verified, <code>false</code>
	 * otherwise.
	 * <p>
	 * This method shows a message box, if a validation fails.
	 * </p>
	 * This is a alternative method for {@link #validateFormularComponents()},
	 * but this method returns a boolean depending on the validation's result,
	 * not the result itself.
	 * 
	 * @return <code>true</code> if all values of all {@link FormularComponent}s
	 *         of this {@link XdevFormular} could be verified,
	 *         <code>false</code> otherwise.
	 */
	public boolean verifyFormularComponents()
	{
		Validation validation = new Validation();
		FormularUtils.validateFormularComponents(this,validation);
		if(validation.hasError())
		{
			ValidationException ve = validation.getExceptionsOf(Severity.ERROR)[0];
			UIUtils.showMessage(ve.getTitle(),ve.getMessage(),MessageDialogType.ERROR_MESSAGE);
			
			return false;
		}
		
		return true;
	}
	
	
	/**
	 * Validates all {@link FormularComponent}s of this formular.
	 * <p>
	 * Example:
	 * 
	 * <pre>
	 * if(formular.validateFormularComponents().hasError())
	 * {
	 * 	// show message
	 * }
	 * else
	 * {
	 * 	formular.save();
	 * }
	 * </pre>
	 * 
	 * @return the validation object containing all occured exceptions
	 * 
	 * @see Validation#hasError()
	 * 
	 * @since 3.1
	 */
	public Validation validateFormularComponents()
	{
		return validateFormularComponents(new Validation());
	}
	
	
	/**
	 * Validates all {@link FormularComponent}s of this formular depending on an
	 * given validation object.
	 * 
	 * @param validation
	 * @return the given validation object
	 * 
	 * @see Validation#hasError()
	 * 
	 * @since 3.1
	 */
	public Validation validateFormularComponents(Validation validation)
	{
		FormularUtils.validateFormularComponents(this,validation);
		return validation;
	}
	
	
	/**
	 * Submits the contents of this {@link XdevFormular} to the given
	 * <code>url</code>.
	 * 
	 * @param url
	 *            url to send the contents to
	 * @param target
	 *            HTML target (<code>_blank</code>, <code>_parent</code> etc.)
	 * @throws IOException
	 *             if the submit fails
	 */
	public void submit(String url, String target) throws IOException
	{
		Application.getContainer().showDocument(new URL(url + getURLAdd()),target);
	}
	
	
	/**
	 * Returns a parameter list consisting of key value pairs containing all
	 * components of this {@link XdevFormular} and their values.
	 * 
	 * e.g. ?txtFirstname=John&txtLastname=Doe
	 * 
	 * @return a parameter list consisting of key value pairs containing all
	 *         components of this {@link XdevFormular} and their values.
	 */
	public String getURLAdd()
	{
		final StringBuffer urlAdd = new StringBuffer();
		final Vector<XdevRadioButton> usedRadioButtons = new Vector<XdevRadioButton>();
		
		lookupComponentTree(new ComponentTreeVisitor<Object, Component>()
		{
			public Object visit(Component cpn)
			{
				if(!(cpn instanceof XdevRadioButton && usedRadioButtons.contains(cpn)))
				{
					Object value = UIUtils.getValue(cpn);
					if(value != null)
					{
						urlAdd.append(urlAdd.length() == 0 ? '?' : '&');
						urlAdd.append(FormularUtils.getName(cpn,true));
						
						if(cpn instanceof XdevRadioButton)
						{
							XdevRadioButton radio = (XdevRadioButton)cpn;
							ButtonGroup buttonGroup = radio.getButtonGroup();
							if(buttonGroup != null)
							{
								Enumeration<AbstractButton> e = buttonGroup.getElements();
								while(e.hasMoreElements())
								{
									Object o = e.nextElement();
									if(o instanceof XdevRadioButton)
									{
										radio = (XdevRadioButton)o;
										if(radio.isSelected())
										{
											value = UIUtils.getValue(radio);
										}
										usedRadioButtons.add(radio);
									}
								}
							}
						}
						
						urlAdd.append('=');
						urlAdd.append(NetUtils.encodeURLString(value.toString()));
					}
				}
				
				return null;
			}
		});
		
		return urlAdd.toString();
	}
	
	
	/**
	 * Creates a query (SELECT * FROM ..) of the connected {@link VirtualTable}
	 * with a {@link WHERE} condition created by the values of the
	 * {@link FormularComponent}s of this formular.
	 * 
	 * @param connector
	 *            "AND" or "OR"
	 * @return The default query info of the connected {@link VirtualTable} with
	 *         a {@link WHERE} condition created by the values of the
	 *         {@link FormularComponent}s of this formular.
	 * @throws IllegalStateException
	 *             if no data binding is available
	 * @throws IllegalArgumentException
	 *             if connector != "AND" resp. "OR"
	 * @see #createCondition(String, Collection)
	 * @see #search(String, VirtualTableOwner)
	 * @since 3.1
	 */
	public QueryInfo createQuery(String connector) throws IllegalStateException,
			IllegalArgumentException
	{
		VirtualTable vt = getVirtualTable();
		if(vt == null)
		{
			throw new IllegalStateException("No data binding available");
		}
		
		SELECT select = vt.getSelect();
		Collection paramCollection = new ArrayList();
		Condition where = createCondition(connector,paramCollection);
		if(where != null)
		{
			select.WHERE(where);
		}
		return new QueryInfo(select,paramCollection.toArray());
	}
	
	
	/**
	 * <p>
	 * Alias for <code>createCondition(connector,null)</code>.
	 * </p>
	 * 
	 * @param connector
	 *            "AND" or "OR"
	 * 
	 * @return The {@link WHERE} condition depending on the input component's
	 *         values, or <code>null</code> if no values are available
	 * 
	 * @throws IllegalArgumentException
	 *             if connector != "AND" resp. "OR"
	 * 
	 * @see #createCondition(String, Query)
	 * @see #createCondition(String, Collection)
	 * @see #createQuery(String)
	 */
	public Condition createCondition(String connector) throws IllegalArgumentException
	{
		return createCondition(connector,(Query)null);
	}
	
	
	/**
	 * Creates a {@link Condition} depending on the input component's values and
	 * their conditional settings.
	 * 
	 * @param connector
	 *            "AND" or "OR"
	 * @param query
	 *            The {@link Query} to create this {@link WHERE} condition for
	 *            (optional)
	 * @return The {@link WHERE} condition depending on the input component's
	 *         values, or <code>null</code> if no values are available
	 * @throws IllegalArgumentException
	 *             if connector != "AND" resp. "OR"
	 * @see #createCondition(String, Collection)
	 * @see #createQuery(String)
	 */
	public Condition createCondition(String connector, final Query query)
			throws IllegalArgumentException
	{
		Map<String, List<FormularValue>> map = new HashMap<String, List<FormularValue>>();
		FormularUtils.collectFormularValues(this,map,false);
		
		ParameterContainer paramContainer = new ParameterContainer()
		{
			@Override
			public void addParameters(Object... params)
			{
				if(query != null)
				{
					query.addParameters(params);
				}
			}
		};
		
		return FormularUtils.createWhereCondition(connector,paramContainer,map);
	}
	
	
	/**
	 * Creates a {@link Condition} depending on the input component's values and
	 * their conditional settings.
	 * 
	 * @param connector
	 *            "AND" or "OR"
	 * @param paramCollection
	 *            The {@link Collection} to add the {@link WHERE} condition's
	 *            parameter to (optional)
	 * @return The {@link WHERE} condition depending on the input component's
	 *         values, or <code>null</code> if no values are available
	 * @throws IllegalArgumentException
	 *             if connector != "AND" resp. "OR"
	 * @see #createCondition(String, Query)
	 * @see #createQuery(String)
	 * @since 3.1
	 */
	public Condition createCondition(String connector, final Collection paramCollection)
			throws IllegalArgumentException
	{
		Map<String, List<FormularValue>> map = new HashMap<String, List<FormularValue>>();
		FormularUtils.collectFormularValues(this,map,false);
		
		ParameterContainer paramContainer = new ParameterContainer()
		{
			@Override
			public void addParameters(Object... params)
			{
				if(paramCollection != null)
				{
					CollectionUtils.addAll(paramCollection,params);
				}
			}
		};
		
		return FormularUtils.createWhereCondition(connector,paramContainer,map);
	}
	
	
	/**
	 * Updates the model of the {@link VirtualTableOwner} target.
	 * <p>
	 * First a {@link Condition} depending on the input component's values and
	 * their conditional settings is created and then handed over to
	 * {@link VirtualTableOwner#updateModel(Condition, Object...)} of
	 * <code>target</code>.
	 * 
	 * @param connector
	 *            "AND" or "OR"
	 * @param target
	 *            the component to update
	 * @throws IllegalArgumentException
	 *             if connector != "AND" resp. "OR"
	 * 
	 * @see #createCondition(String)
	 * @see #createCondition(String, Collection)
	 * @see #createCondition(String, Query)
	 * @see #createQuery(String)
	 */
	public void search(String connector, VirtualTableOwner target) throws IllegalArgumentException
	{
		Map<String, List<FormularValue>> map = new HashMap<String, List<FormularValue>>();
		FormularUtils.collectFormularValues(this,map,false);
		
		final List paramList = new ArrayList();
		ParameterContainer paramContainer = new ParameterContainer()
		{
			@Override
			public void addParameters(Object... params)
			{
				Collections.addAll(paramList,params);
			}
		};
		
		Condition condition = FormularUtils.createWhereCondition(connector,paramContainer,map);
		target.updateModel(condition,paramList.toArray());
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@BeanProperty(category = DefaultBeanCategories.MISC)
	@Override
	public void setForeground(Color fg)
	{
		super.setForeground(fg);
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@BeanProperty(category = DefaultBeanCategories.MISC)
	@Override
	public void setFont(Font font)
	{
		super.setFont(font);
	}
}
